// 《围棋》作者版权所有。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// 开始：构建！plan9 

package lockedfile

import (
	"io/fs"
	"os"

	"cmd/go/internal/lockedfile/internal/filelock"
)

func openFile(name string, flag int, perm fs.FileMode) (*os.File, error) {
	// 在BSD系统上，我们可以将O_SHLOCK或O_EXLOCK标志添加到OpenFile 
	// 调用中，而不是单独锁定，但我们必须支持单独锁定
	// 对Linux和Windows的调用，所以一致使用这种方法
	// 更简单。

	f, err := os.OpenFile(name, flag&^os.O_TRUNC, perm)
	if err != nil {
		return nil, err
	}

	switch flag & (os.O_RDONLY | os.O_WRONLY | os.O_RDWR) {
	case os.O_WRONLY, os.O_RDWR:
		err = filelock.Lock(f)
	default:
		err = filelock.RLock(f)
	}
	if err != nil {
		f.Close()
		return nil, err
	}

	if flag&os.O_TRUNC == os.O_TRUNC {
		if err := f.Truncate(0); err != nil {
			// 操作系统的文档。O_TRUNC说“如果可能，在打开
			// 时截断文件”，但没有定义“可能”（golang.org/issue/28699）。
			// 我们将把常规文件（以及指向常规文件的符号链接）视为“可能”
			// 并忽略其他错误。
			if fi, statErr := f.Stat(); statErr != nil || fi.Mode().IsRegular() {
				filelock.Unlock(f)
				f.Close()
				return nil, err
			}
		}
	}

	return f, nil
}

func closeFile(f *os.File) error {
	// 由于锁定系统调用对文件描述符进行操作，我们必须在描述符仍然有效时（即在文件关闭之前）解锁文件
	// 并避免解锁已关闭的文件。
	err := filelock.Unlock(f)

	if closeErr := f.Close(); err == nil {
		err = closeErr
	}
	return err
}
